Text Window
Volume Number: 1
Issue Number: 6
Column Tag: C WORKSHOP
Windows for Text Editing
By Robert B. Denny
Windows for Text Editing
One of the most comon uses of windows in Macintosh applications is for
manipulation of text. The user interface for text editing is one of the cornerstones of
Mac technology.
This month’s C Workshop deals with implementing editing windows. We’ll bring
together much of what was presented in previous columns, and add new informa- tion
on TextEdit’s services. We’ll also show some specifics on using the Control Manager to
handle scroll bars, and their use in scrolling our view of text managed by TextEdit.
One rather important issue that will not be covered is file I/O. It’s important to
realize that TextEdit manages opera- tions on text in memory. If you want to work
with text in a file, you must provide the services for getting text into and out of
memory in manageable chunks for TextEdit to work with.
TextEdit Power
TextEdit is one of the most powerful of Macintosh’s system service packages. The
Mac designers realized that editing text is probably the most pervasive user operation
of all, and that standardizing the editing interface would make the Mac easy to use.
Therefore, they have pro- vided us with a remarkably flexible and user-friendly
editing package.
TextEdit consists of a set of system services for displaying and manipulating text.
Text is stored in coded form in a linear array hooked to a master data structure called a
TERec. The TERec is to TextEdit as the WindowRecord is to the Window Manager.
The display services of TextEdit handle drawing of text in the window, line
breaking with optional word wrap, blink- ing the caret and showing selected ranges of
text. The manipulating services handle text selection, insertion, deletion, and the scrap
services of copy, cut and paste, operating on the actual text stored in memory and on
the clipboard. It is a good idea to keep the concepts of display and manipulation separate
in your mind.
TextEdit is reasonably documented in Inside Macintosh. It can be difficult to keep
an overall perspective on things, though, when an editing window involves so many of
Macintosh’s services and their data structures. Figure 1 shows a bird’s eye view of
most of the data structures involved in an editing window.
Using TextEdit: Statics
There are three main things to get set up in preparation for using TextEdit with a
window. First, you must create a TERec with a filled-in handle to the array where the
text will be stored. Next, you must define two very important rectangles, the
ViewRect and the DestRect. First, let’s look at the TERec. As usual, its structure will
tell us some important things about TextEdit. The names of the structure members
have been taken directly from the Lisa Pascal interface to TextEdit in the file
TOOLINTF.TEXT.
The meaning of most of the fields is shown in IM. We’ll document some of the
others, though you won’t normally use them. “selPoint” is the mouseLoc of the
current selection point. “active” is a boolean telling whether the related window is
active. This is used to control caret blinking and highlighting of selected text during
window updates. “wordBreak” is a pointer to the routine that calculates word breaks
(this has interesting possi- bilities). “clickLoop” is a pointer to the routine called
when the mouse is clicked in text. It handles tracking the mouse and highlighting the
selected text on the fly while the mouse is pressed. “clickTime” and “clickLoc” mark
the place and time where & when the mouse was first clicked. Presumably, they are
the property of the clickLoop routine. “recalBack” is a boolean that controls whether
line breaks will be continuously recalculated in background or not and “recalLines” is
a boolean that indicates whether a recalculation is under way. Finally, “caretState”
indicates whether the caret is on or not, and “caretTime” contains the time for the
next caret blink.
struct TERec
{
Rect destRect;
Rect viewRect;
Rect selRect;
short lineHeight;
short firstBL;
Point selPoint;
short selStart;
short selEnd;
short active;
long wordBreak;
long clikLoop;
long clickTime;
short clickLoc;
long caretTime;
short caretState;
short just;
short TElength;
Handle hText;
short recalBack;
short recalLines;
short clikStuff;
short crOnly;
short txFont;
Style txFace;
short txMode;
short txSize;
GrafPtr inPort;
Ptr highHook;
Ptr caretHook;
short nLines;
short lineStarts[1];
};
Before doing anything, your application must call TEInit(). This allocates a
single common scrap area for use by TextEdit.
To allocate a TERec and a text array for a given window, call SetPort() for that
window, then call TENew(), which returns a handle to the TERec. TENew() doesn’t
take a windowPtr or a grafPtr, rather, it works on the current port, hence the need to
call SetPort() before TENew().
The two parameters to TENew are the destRect and the viewRect. These two
rectangles determine the line width and displayed portion of text. Remember the text
is stored in a linear array hooked to the TERec. Refer again to Fig. 1. Notice that the
TERec contains a set of pointers (actually offsets) to the places in the text where line
breaks occur. How are these calculated?
Figure 2 above shows the relationship between the two rectangles. You can look at
the destRect as the dimensions of the “chalkboard” where the text is to be displayed.
TextEdit automatically mea- sures the length of the text in the storage array (given its
font parameters) and calculates the location of the line breaks, normally employing
word breaks instead of character breaks. The destRect is specified in the coordinate
system of the owning window’s grafPort. Don’t forget this, it has important
implications. In Figure 2, the top left corner of the destRect has negative v & h
coordinate values.
The viewRect specifies the portion of the text that is actually visible. It is sort of
a “clip” rectangle for the text display. Why the distinction? Consider the case of
displaying text as it would print on 8-inch wide paper. The destRect would have a
width of 576 pixels (72 x 8). This sets the line breaks for the paper in use.
But the window itself can be resized and scrolled around on the 8-inch wide
“chalkboard”. The viewRect is used to tell TextEdit the dimensions and location in the
grafPort of the “view-port” to the text display being constructed on the chalk- board.
Normally, the viewRect is set to completely enclose the content region of the
window, less the scroll bar and size box areas. It too is specified in grafPort
coordinates.
One final word about setting up a window for text editing. It’s a good idea to
provide a 3 or 4 pixel “bleed” on the top and left of the page image. There are two
schools of thought on this. One says that the bleed should be present regardless of the
location of the viewRect in the destRect. The other (to which I sub- scribe says that
the bleed should be present only if the destRect is at the left and/or top of the window
port. The latter makes it visually easy to tell whether there is text to the left and
above the viewRect by showing fragments of letters at the window’s edge.
Using TextEdit: Dynamics
Once you have set up a window for editing, you can start manipulating and
displaying text. Keep in mind the following facts about TextEdit:
1. It works only on memory images of text. No file I/O is provided.
2. It does not support multiple of character fonts, styles or sizes in a single
environment.
The simplest operation is insertion of text. This can be done one character at a
time with TEKey() and en masse with TEinsert(). It is most common to use TEKey() in
response to a keypress event. TextEdit sticks the new text into the linear array,
recalculates the downstream line breaks, then posts an update event for the window, if
needed.
Simple deletion is also easy. To delete the character to the left of the current
insertion point or to delete the current selection, call TEKey() with the “backspace”
key code. To delete the current selection (if any), call TEDelete(). TextEdit removes
the deleted text from the linear array, closing up the space, recalculates the the
downstream line breaks, then posts an update event for the window, if needed.
Mass operations of cut, copy and paste are hardly more difficult. Normally, you
should provide both menu options and “menu keys” to trigger these operations, in
accordance with the Macintosh interface guidelines. Call TECut(), TECopy() or
TEPaste() to perform the operation. If there is no text currently selected, these
functions are no-ops. Otherwise, the indicated opera- tion is performed, including
transfers to or from the scrap allocated by TEInit(), and the already described
manipulations of the linear text array and display.
How do we select text with the mouse? Recall that the TERec contains a pointer to
the “clickLoop” routine, the routine responsible for tracking the mouse while it is
kept pressed and highlighting selected text on the fly as the mouse is moved. How does
this process get started? By calling TEClick().
So whenever your application detects a mouse press inside the viewRect of an
editing window, it should call TEClick(). You can determine whether the mouse was
pressed in the viewRect by calling the QuickDraw ptInRect() service with the clicked
mouse location and the viewRect. If it returns TRUE, then call TEClick(). The
“clickLoop” routine is entered and continues to execute until the mouse is released, at
which time control is returned to the statement following the call to TEClick().
To implement the “shift-click” method of extended selection, you must
determine whether the shift key was pressed at the time the mouse was clicked and
pass TRUE for the “ext” parameter to the TEClick() call. For more informa- tion, see
IM.
To scroll the text display in the viewRect, simply call TEScroll(). This function
takes as parameters the distance (in pixels) to scroll horizontally and vertically. A
zero means no scrolling in that direction.
Now let’s test your understanding of TextEdit’s coordinate system. The first
person write in with the correct answer to the following question will receive a free
copy of the MacTutor “source disk”:
QUESTION:
Suppose there was no TEScroll() function. How would you scroll the displayed
text? Provide a C language implementation of TEScroll():
tescroll(dh, dv, th)
short int dh; /* Horiz amount */